#include <db_cxx.h>
#include <iostream>
#include <fstream>
#include <string.h>
#include <stdlib.h>
#include "data.h"
#include "paper.pb.h"
#include <stdio.h>
#include <QUuid>
#include <QByteArray>
#include <QDataStream>
#include <QTextStream>

int get_papers(Db* sdbp, const Dbt *pkey, const Dbt *pdata, Dbt *skey)
{
    //biblio::Paper paper;
    std::cout << "get_papers called\n";
    std::cout.flush();
    biblio::Key key;
    //std::string buf((const char*) pkey->get_data());
    //paper->ParseFromString(buf);
    key.ParseFromArray(pkey->get_data(), pkey->get_size());
    std::cout << "1\n";
    std::cout.flush();
    if (key.table() != biblio::Key_TableType_TABLE_PAPERS) {
        std::cout << "key.table" << key.table() << biblio::Key_TableType_TABLE_PAPERS << "\n";
        std::cout.flush();
        return DB_DONOTINDEX;
    }
    std::cout << "2\n";
    std::cout.flush();
    void *keybuf;
    keybuf = malloc(pkey->get_size());
    memcpy(keybuf, pkey->get_data(), pkey->get_size());
    std::cout << "3\n";
    std::cout.flush();
    skey->set_data(keybuf);
    skey->set_size(pkey->get_size());
    std::cout << "end\n";
    std::cout.flush();
    return 0;
}

// Class constructor. Requires a path to the location
// where the database is located, and a database name
BiblioDb::BiblioDb(std::string &path, std::string &dbName)
        : dbFileName_(path + dbName), cFlags_(DB_CREATE)
{
    db_ = NULL;
    // Redirect debugging information to std::cerr
    open();
}

void BiblioDb::open()
{
    try {
        // TODO check if already open
        db_ = new Db(0, NULL);
        db_papers = new Db(0, NULL);
        db_->set_error_stream(&std::cerr);
        db_papers->set_error_stream(&std::cerr);
        // Open the database
        std::cout << dbFileName_.c_str() << " Flags: " << cFlags_ << "\n";
        std::cout.flush();
        db_->open(NULL, dbFileName_.c_str(), "main", DB_BTREE, cFlags_, 0);
        db_papers->open(NULL, dbFileName_.c_str(), "papers", DB_BTREE, cFlags_, 0);
        db_->associate(NULL, db_papers, get_papers, 0);
        cursorp = NULL;
    }
    // DbException is not a subclass of std::exception, so we
    // need to catch them both.
    catch (DbException &e)
    {
        std::cerr << "Error opening database: " << dbFileName_ << "\n";
        std::cerr << e.what() << std::endl;
    }
    catch (std::exception &e)
    {
        std::cerr << "Error opening database: " << dbFileName_ << "\n";
        std::cerr << e.what() << std::endl;
    }
}

// Private member used to close a database. Called from the class
// destructor.
void BiblioDb::close()
{
    std::cout << "Close" << "\n";
    std::cout.flush();
    // Close the cursor
    if (cursorp != NULL) {
        cursorp->close();
    }
    // Close the db
    try
    {
        db_papers->close(0);
        db_->close(0);
        delete db_;
        delete db_papers;
        std::cout << "Database " << dbFileName_ << " is closed." << std::endl;
    }
    catch (DbException &e)
    {
        std::cerr << "Error closing database: " << dbFileName_ << "\n";
        std::cerr << e.what() << std::endl;
    }
    catch (std::exception &e)
    {
        std::cerr << "Error closing database: " << dbFileName_ << "\n";
        std::cerr << e.what() << std::endl;
    }
}

qint64 BiblioDb::generate_uuid()
{
    
    QUuid uuid = QUuid::createUuid();
    QByteArray byte_array;
    QByteArray uuid_array;
    qint64 shortuuid = 10; // TODO make this a 128 bit one and change the data structure
    QDataStream uuid_stream(&byte_array, QIODevice::ReadWrite);
    shortuuid = (qint64) uuid.data4[0] |
                (qint64) uuid.data4[1] << 8 |
                (qint64) uuid.data4[2] << 16 |
                (qint64) uuid.data4[3] << 24 |
                (qint64) uuid.data4[4] << 32 |
                (qint64) uuid.data4[5] << 40 |
                (qint64) uuid.data4[6] << 48 |
                (qint64) uuid.data4[7] << 56;
    std::cout << uuid.toString().toStdString() << " " << shortuuid << "\n";
    std::cout.flush();
    return shortuuid;
}

int BiblioDb::saveNewData(biblio::Paper *paper)
{
    void *keybuf;
    void *databuf;
    int ret;
    //std::string strkeybuf;
    std::string strdatabuf;
    if (cursorp == NULL) {
        db_papers->cursor(NULL, &cursorp, 0);
    }
    qint64 attachid = create_attachment_data("/home/patrick/tmp/lorem.txt");
    std::cout << "ATTACHID: " << attachid << "\n";
    std::cout.flush();
    qint64 shortuuid = generate_uuid(); 
    //uuid_stream << uuid;
    //uuid_stream >> shortuuid;

    key.set_table(biblio::Key::TABLE_PAPERS);
    key.set_id(shortuuid);
    keybuf = malloc(key.ByteSize());
    int keysize = key.ByteSize();
    key.SerializeToArray(keybuf, keysize);
    
    keydbt.set_data(keybuf);
    keydbt.set_size(keysize);

    paper->set_attachment(attachid);
    int datasize = paper->ByteSize();
    databuf = malloc(datasize);
    paper->SerializeToArray(databuf, datasize);
    std::cout << "databuflen write: " << datasize << db_ << "\n";
    std::cout.flush();
    Dbt datadbt(databuf, datasize);
    ret = db_->put(NULL, &keydbt, &datadbt, DB_NOOVERWRITE);
    if (ret) {
        db_->err(ret, "Put failed because key already exists");
    }
    std::cout << "before new read" << cursorp << "\n ";
    std::cout.flush();
    int cursor_ret = cursorp->get(&keydbt, &datadbt, DB_SET);
    std::cout << "after read\n ";
    std::cout.flush();
    if (cursor_ret == 0) {
        std::string buf((const char*) datadbt.get_data());
        std::cout << "databuflen read: \n";
        std::cout.flush();
        std::cout << datadbt.get_size() << "\n";
        std::cout.flush();
        paper->ParseFromArray(datadbt.get_data(), datadbt.get_size());
        int keysize = key.ByteSize();
        key.ParseFromArray(keydbt.get_data(), keysize);
            
        if (paper->has_attachment()) {
            std::cout << "ATTACHID READ WITH ATTACHMENT: " << paper->attachment() << "\n";
            std::cout.flush();
        } else {
            std::cout << "ATTACHID READ WITHOUT ATTACHMENT: " << paper->attachment() << "\n";
            std::cout.flush();
        }
        return 0;
    }
    
    free(keybuf);
    free(databuf);
    return ret;
}

int BiblioDb::saveData(biblio::Paper *paper)
{
    QTextStream qout(stdout);
    qout << "Overwriting data" << key.id() << endl;
    
    void *keybuf;
    void *databuf;
    int ret;
    std::string strkeybuf;
    std::string strdatabuf;

    int keysize = key.ByteSize();
    keybuf = malloc(keysize);
    key.SerializeToArray(keybuf, keysize);
    
    keydbt.set_data(keybuf);
    keydbt.set_size(keysize);

    int datasize = paper->ByteSize();
    databuf = malloc(datasize);
    paper->SerializeToArray(databuf, datasize);
    
    Dbt datadbt(databuf, datasize);
    ret = db_->put(NULL, &keydbt, &datadbt, 0);
    if (ret) {
        db_->err(ret, "Put failed because key already exists");
    }
    free(keybuf);
    free(databuf);
    return ret;
}

int BiblioDb::nextData(biblio::Paper *paper)
{
    std::cout << "nextData "  << "\n";
    std::cout.flush();
    long id;
    int ret;

    Dbt dbt_key, dbt_data;
    
    if (cursorp == NULL) {
        std::cout << "Create new cursor\n"; std::cout.flush();
        db_papers->cursor(NULL, &cursorp, 0);
    }
    
    cursor_ret = cursorp->get(&dbt_key, &dbt_data, DB_NEXT);

    if (cursor_ret == 0) {
        //cursor_info = CURSOR_INFO_NORMAL;
        paper->ParseFromArray(dbt_data.get_data(), dbt_data.get_size());
        //std::string kbuf((const char*) keydbt.get_data());
        //key.ParseFromArray(dbt_key.get_data(), dbt_key.get_size());
        return 0;
    }
    std::cout << "No key found" << cursor_ret << " " << DB_NOTFOUND << "\n" ;
    std::cout.flush();
    return 1;
}

int BiblioDb::previousData(biblio::Paper *paper)
{
    Dbt datadbt;
    long id;
    int ret;

    Dbt dbt_key, dbt_data;

    if (cursorp == NULL) {
        std::cout << "Create new cursor\n"; std::cout.flush();
        db_papers->cursor(NULL, &cursorp, 0);
    }
    
    std::cout << "previousData " << "\n";

    if (cursorp == NULL) {
        db_papers->cursor(NULL, &cursorp, 0);
    }
    cursor_ret = cursorp->get(&dbt_key, &dbt_data, DB_PREV);
    
    if (cursor_ret == 0) {
        paper->ParseFromArray(dbt_data.get_data(), dbt_data.get_size());
        //key.ParseFromArray(dbt_key.get_data(), dbt_key.get_size());
        return 0;
    }
    std::cout << "No key found" << cursor_ret << " " << DB_NOTFOUND << "\n" ;
    std::cout.flush();
    return 1;
}

qint64 BiblioDb::create_attachment_data(const char* filename)
{
    int ret;
    std::string strkeybuf;
    std::string strdatabuf;
    biblio::Key attach_key;
    biblio::Attachment attachment;
    char* attachment_buffer;
    char *key_buffer;
    int key_size;
    int attachment_size;
    
    std::cout << filename << "\n"; std::cout.flush();
    // TODO load file content
    attachment.set_blob(filename, strlen(filename));
    std::cout << attachment.blob() << "\n"; std::cout.flush();
    
    qint64 shortuuid = generate_uuid(); 
    //uuid_stream << uuid;
    //uuid_stream >> shortuuid;

    attach_key.set_table(biblio::Key::TABLE_ATTACHMENTS);
    attach_key.set_id(shortuuid);
    key_size = attach_key.ByteSize();
    key_buffer = (char*) malloc(key_size);
    attach_key.SerializeToArray(key_buffer, key_size);
    keydbt.set_data(key_buffer);
    keydbt.set_size(key_size);

    //databuf = malloc(strlen(data)+1);
    //memcpy(databuf, data, strlen(data)+1);
    attachment_buffer = (char*) malloc(attachment.ByteSize());
    std::cout << attachment.blob() << "\n"; std::cout.flush();
    attachment_size = attachment.ByteSize();
    attachment.SerializeToArray(attachment_buffer, attachment_size);
    std::cout << "Attachment Size: " << attachment_size << "\n";
    std::cout.flush();
    Dbt datadbt(attachment_buffer, attachment_size);
    //datadbt = new Dbt(databuf, strdatabuf.size());
    ret = db_->put(NULL, &keydbt, &datadbt, DB_NOOVERWRITE);
    if (ret) {
        db_->err(ret, "Put failed because key already exists");
    }
    free(attachment_buffer);
    free(key_buffer);
    return shortuuid;
}

int BiblioDb::get_attachment(biblio::Attachment *attachment, biblio::Paper paper)
{
    biblio::Key key;
    qint64 id;
    Dbt ddbt;
    Dbt kdbt;
    char *keybuf;
    int keysize;
    id = paper.attachment();
    key.set_id(id);
    key.set_table(biblio::Key::TABLE_ATTACHMENTS);
    keysize = key.ByteSize();
    keybuf = (char*) malloc(keysize);
    key.SerializeToArray(keybuf, keysize);
    kdbt.set_data(keybuf);
    kdbt.set_size(keysize);
    db_->get(NULL, &kdbt, &ddbt, 0);
    std::cout << "Got attachment Size: " << ddbt.get_size() << "\n";
    std::cout.flush();
    attachment->ParseFromArray(ddbt.get_data(), ddbt.get_size());
    return 0;
}